﻿using Avalonia;
using Avalonia.Controls;
using Avalonia.Media;
using Avalonia.Platform.Storage;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;

namespace SourceGit.ViewModels {
    public class CommitDetail : ObservableObject {
        public DiffContext DiffContext {
            get => _diffContext;
            private set => SetProperty(ref _diffContext, value);
        }

        public int ActivePageIndex {
            get => _activePageIndex;
            set => SetProperty(ref _activePageIndex, value);
        }

        public Models.Commit Commit {
            get => _commit;
            set {
                if (SetProperty(ref _commit, value)) Refresh();
            }
        }

        public List<Models.Change> Changes {
            get => _changes;
            set => SetProperty(ref _changes, value);
        }

        public List<Models.Change> VisibleChanges {
            get => _visibleChanges;
            set => SetProperty(ref _visibleChanges, value);
        }

        public List<FileTreeNode> ChangeTree {
            get => _changeTree;
            set => SetProperty(ref _changeTree, value);
        }

        public Models.Change SelectedChange {
            get => _selectedChange;
            set {
                if (SetProperty(ref _selectedChange, value)) {
                    if (value == null) {
                        SelectedChangeNode = null;
                        DiffContext = null;
                    } else {
                        SelectedChangeNode = FileTreeNode.SelectByPath(_changeTree, value.Path);
                        DiffContext = new DiffContext(_repo, new Models.DiffOption(_commit, value));
                    }
                }
            }
        }

        public FileTreeNode SelectedChangeNode {
            get => _selectedChangeNode;
            set {
                if (SetProperty(ref _selectedChangeNode, value)) {
                    if (value == null) {
                        SelectedChange = null;
                    } else {
                        SelectedChange = value.Backend as Models.Change;
                    }
                }
            }
        }

        public string SearchChangeFilter {
            get => _searchChangeFilter;
            set {
                if (SetProperty(ref _searchChangeFilter, value)) {
                    RefreshVisibleChanges();
                }
            }
        }

        public List<FileTreeNode> RevisionFilesTree {
            get => _revisionFilesTree;
            set => SetProperty(ref _revisionFilesTree, value);
        }

        public FileTreeNode SelectedRevisionFileNode {
            get => _selectedRevisionFileNode;
            set {
                if (SetProperty(ref _selectedRevisionFileNode, value) && value != null && !value.IsFolder) {
                    RefreshViewRevisionFile(value.Backend as Models.Object);
                } else {
                    ViewRevisionFileContent = null;
                }
            }
        }

        public string SearchFileFilter {
            get => _searchFileFilter;
            set {
                if (SetProperty(ref _searchFileFilter, value)) {
                    RefreshVisibleFiles();
                }
            }
        }

        public object ViewRevisionFileContent {
            get => _viewRevisionFileContent;
            set => SetProperty(ref _viewRevisionFileContent, value);
        }

        public CommitDetail(string repo) {
            _repo = repo;
        }

        public void NavigateTo(string commitSHA) {
            var repo = Preference.FindRepository(_repo);
            if (repo != null) repo.NavigateToCommit(commitSHA);
        }

        public void ClearSearchChangeFilter() {
            SearchChangeFilter = string.Empty;
        }

        public void ClearSearchFileFilter() {
            SearchFileFilter = string.Empty;
        }

        public ContextMenu CreateChangeContextMenu(Models.Change change) {
            var menu = new ContextMenu();

            if (change.Index != Models.ChangeState.Deleted) {
                var history = new MenuItem();
                history.Header = App.Text("FileHistory");
                history.Icon = CreateMenuIcon("Icons.Histories");
                history.Click += (_, ev) => {
                    var window = new Views.FileHistories() { DataContext = new FileHistories(_repo, change.Path) };
                    window.Show();
                    ev.Handled = true;
                };

                var blame = new MenuItem();
                blame.Header = App.Text("Blame");
                blame.Icon = CreateMenuIcon("Icons.Blame");
                blame.Click += (o, ev) => {
                    var window = new Views.Blame() { DataContext = new Blame(_repo, change.Path, _commit.SHA) };
                    window.Show();
                    ev.Handled = true;
                };

                var full = Path.GetFullPath(Path.Combine(_repo, change.Path));
                var explore = new MenuItem();
                explore.Header = App.Text("RevealFile");
                explore.Icon = CreateMenuIcon("Icons.Folder.Open");
                explore.IsEnabled = File.Exists(full);
                explore.Click += (_, ev) => {
                    Native.OS.OpenInFileManager(full, true);
                    ev.Handled = true;
                };

                menu.Items.Add(history);
                menu.Items.Add(blame);
                menu.Items.Add(explore);
            }

            var copyPath = new MenuItem();
            copyPath.Header = App.Text("CopyPath");
            copyPath.Icon = CreateMenuIcon("Icons.Copy");
            copyPath.Click += (_, ev) => {
                App.CopyText(change.Path);
                ev.Handled = true;
            };

            menu.Items.Add(copyPath);
            return menu;
        }

        public ContextMenu CreateRevisionFileContextMenu(Models.Object file) {
            var history = new MenuItem();
            history.Header = App.Text("FileHistory");
            history.Icon = CreateMenuIcon("Icons.Histories");
            history.Click += (_, ev) => {
                var window = new Views.FileHistories() { DataContext = new FileHistories(_repo, file.Path) };
                window.Show();
                ev.Handled = true;
            };

            var blame = new MenuItem();
            blame.Header = App.Text("Blame");
            blame.Icon = CreateMenuIcon("Icons.Blame");
            blame.Click += (o, ev) => {
                var window = new Views.Blame() { DataContext = new Blame(_repo, file.Path, _commit.SHA) };
                window.Show();
                ev.Handled = true;
            };

            var full = Path.GetFullPath(Path.Combine(_repo, file.Path));
            var explore = new MenuItem();
            explore.Header = App.Text("RevealFile");
            explore.Icon = CreateMenuIcon("Icons.Folder.Open");
            explore.Click += (_, ev) => {
                Native.OS.OpenInFileManager(full, file.Type == Models.ObjectType.Blob);
                ev.Handled = true;
            };

            var saveAs = new MenuItem();
            saveAs.Header = App.Text("SaveAs");
            saveAs.Icon = CreateMenuIcon("Icons.Save");
            saveAs.IsEnabled = file.Type == Models.ObjectType.Blob;
            saveAs.Click += async (_, ev) => {
                var topLevel = App.GetTopLevel();
                if (topLevel == null) return;

                var options = new FolderPickerOpenOptions() { AllowMultiple = false };
                var selected = await topLevel.StorageProvider.OpenFolderPickerAsync(options);
                if (selected.Count == 1) {
                    var saveTo = Path.Combine(selected[0].Path.LocalPath, Path.GetFileName(file.Path));
                    Commands.SaveRevisionFile.Run(_repo, _commit.SHA, file.Path, saveTo);
                }

                ev.Handled = true;
            };

            var copyPath = new MenuItem();
            copyPath.Header = App.Text("CopyPath");
            copyPath.Icon = CreateMenuIcon("Icons.Copy");
            copyPath.Click += (_, ev) => {
                App.CopyText(file.Path);
                ev.Handled = true;
            };

            var menu = new ContextMenu();
            menu.Items.Add(history);
            menu.Items.Add(blame);
            menu.Items.Add(explore);
            menu.Items.Add(saveAs);
            menu.Items.Add(copyPath);
            return menu;
        }

        private void Refresh() {
            _changes = null;
            VisibleChanges = null;
            SelectedChange = null;
            RevisionFilesTree = null;
            SelectedRevisionFileNode = null;
            if (_commit == null) return;
            if (_cancelToken != null) _cancelToken.Requested = true;

            _cancelToken = new Commands.Command.CancelToken();
            var cmdChanges = new Commands.QueryCommitChanges(_repo, _commit.SHA) { Cancel = _cancelToken };
            var cmdRevisionFiles = new Commands.QueryRevisionObjects(_repo, _commit.SHA) { Cancel = _cancelToken };

            Task.Run(() => {
                var changes = cmdChanges.Result();
                if (cmdChanges.Cancel.Requested) return;

                var visible = changes;
                if (!string.IsNullOrWhiteSpace(_searchChangeFilter)) {
                    visible = new List<Models.Change>();
                    foreach (var c in changes) {
                        if (c.Path.Contains(_searchChangeFilter, StringComparison.OrdinalIgnoreCase)) {
                            visible.Add(c);
                        }
                    }
                }

                var tree = FileTreeNode.Build(visible);
                Dispatcher.UIThread.Invoke(() => {
                    Changes = changes;
                    VisibleChanges = visible;
                    ChangeTree = tree;
                });
            });

            Task.Run(() => {
                var files = cmdRevisionFiles.Result();
                if (cmdRevisionFiles.Cancel.Requested) return;

                var visible = files;
                if (!string.IsNullOrWhiteSpace(_searchFileFilter)) {
                    visible = new List<Models.Object>();
                    foreach (var f in files) {
                        if (f.Path.Contains(_searchFileFilter, StringComparison.OrdinalIgnoreCase)) {
                            visible.Add(f);
                        }
                    }
                }

                var tree = FileTreeNode.Build(visible);
                Dispatcher.UIThread.Invoke(() => {
                    _revisionFiles = files;
                    RevisionFilesTree = tree;
                });
            });
        }

        private void RefreshVisibleChanges() {
            if (_changes == null) return;

            if (string.IsNullOrEmpty(_searchChangeFilter)) {
                VisibleChanges = _changes;
            } else {
                var visible = new List<Models.Change>();
                foreach (var c in _changes) {
                    if (c.Path.Contains(_searchChangeFilter, StringComparison.OrdinalIgnoreCase)) {
                        visible.Add(c);
                    }
                }

                VisibleChanges = visible;
            }

            ChangeTree = FileTreeNode.Build(_visibleChanges);
        }

        private void RefreshVisibleFiles() {
            if (_revisionFiles == null) return;

            var visible = _revisionFiles;
            if (!string.IsNullOrWhiteSpace(_searchFileFilter)) {
                visible = new List<Models.Object>();
                foreach (var f in _revisionFiles) {
                    if (f.Path.Contains(_searchFileFilter, StringComparison.OrdinalIgnoreCase)) {
                        visible.Add(f);
                    }
                }
            }

            RevisionFilesTree = FileTreeNode.Build(visible);
        }

        private void RefreshViewRevisionFile(Models.Object file) {
            switch (file.Type) {
            case Models.ObjectType.Blob:
                Task.Run(() => {
                    var isBinary = new Commands.IsBinary(_repo, _commit.SHA, file.Path).Result();
                    if (isBinary) {
                        Dispatcher.UIThread.Invoke(() => {
                            ViewRevisionFileContent = new Models.RevisionBinaryFile();
                        });
                        return;
                    }

                    var content = new Commands.QueryFileContent(_repo, _commit.SHA, file.Path).Result();
                    if (content.StartsWith("version https://git-lfs.github.com/spec/", StringComparison.OrdinalIgnoreCase)) {
                        var obj = new Models.RevisionLFSObject() { Object = new Models.LFSObject() };
                        var lines = content.Split('\n', StringSplitOptions.RemoveEmptyEntries);
                        if (lines.Length == 3) {
                            foreach (var line in lines) {
                                if (line.StartsWith("oid sha256:")) {
                                    obj.Object.Oid = line.Substring(11);
                                } else if (line.StartsWith("size ")) {
                                    obj.Object.Size = long.Parse(line.Substring(5));
                                }
                            }
                            Dispatcher.UIThread.Invoke(() => {
                                ViewRevisionFileContent = obj;
                            });
                            return;
                        }                        
                    }

                    Dispatcher.UIThread.Invoke(() => {
                        ViewRevisionFileContent = new Models.RevisionTextFile() {
                            FileName = file.Path,
                            Content = content 
                        };
                    });
                });
                break;
            case Models.ObjectType.Commit:
                ViewRevisionFileContent = new Models.RevisionSubmodule() { SHA = file.SHA };
                break;
            default:
                ViewRevisionFileContent = null;
                break;
            }
        }

        private Avalonia.Controls.Shapes.Path CreateMenuIcon(string key) {
            var icon = new Avalonia.Controls.Shapes.Path();
            icon.Width = 12;
            icon.Height = 12;
            icon.Stretch = Stretch.Uniform;
            icon.Data = App.Current?.FindResource(key) as StreamGeometry;
            return icon;
        }

        private string _repo = string.Empty;
        private int _activePageIndex = 0;
        private Models.Commit _commit = null;
        private List<Models.Change> _changes = null;
        private List<Models.Change> _visibleChanges = null;
        private List<FileTreeNode> _changeTree = null;
        private Models.Change _selectedChange = null;
        private FileTreeNode _selectedChangeNode = null;
        private string _searchChangeFilter = string.Empty;
        private DiffContext _diffContext = null;
        private List<Models.Object> _revisionFiles = null;
        private List<FileTreeNode> _revisionFilesTree = null;
        private FileTreeNode _selectedRevisionFileNode = null;
        private string _searchFileFilter = string.Empty;
        private object _viewRevisionFileContent = null;
        private Commands.Command.CancelToken _cancelToken = null;
    }
}
